﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;
using Xceed.Silverlight.Data;

namespace Xceed.Silverlight.DataGrid
{
  internal class ChildrenManager<T> where T : class
  {
    public ChildrenManager( Action<T> addAction, Action<T> removeAction )
    {
      if( addAction == null )
        throw new ArgumentNullException( "addAction" );

      if( removeAction == null )
        throw new ArgumentNullException( "removeAction" );

      m_addAction = addAction;
      m_removeAction = removeAction;

    }

    private object GetKey( T element )
    {
      return ( ( IRecyclable )element ).RecycleKey;
    }

    private void SetRecycled( T element, bool recycled )
    {
      ( ( IRecyclable )element ).IsRecycled = recycled;
    }

    private bool GetIsRecycled( T element )
    {
      return ( ( IRecyclable )element ).IsRecycled;
    }

    public IEnumerable<T> RenderedElements
    {
      get
      {
        return m_renderedItems;
      }
    }

    public IEnumerable<T> AllElements
    {
      get
      {
        return m_keyItemDic.Values;
      }
    }

    public bool TryGetElement( object key, out T element )
    {
      if( m_keyItemDic.TryGetValue( key, out element ) )
      {
        if( this.GetIsRecycled( element ) )
        {
          LinkedListNode<T> node = m_recycledItems.Find( element );
          m_recycledItems.Remove( node );
          m_renderedItems.AddLast( element );
        }
        this.SetRecycled( element, false );
        return true;
      }

      this.CheckListsIntegrity();

      return false;
    }

    public void ReuseElement( object newKey, T element )
    {
      if( !m_keyItemDic.Remove( this.GetKey(element) ) )
        throw new InvalidOperationException();

      if( this.GetIsRecycled( element ) )
      {
        LinkedListNode<T> node = m_recycledItems.Find( element );
        m_recycledItems.Remove( node );
        m_renderedItems.AddLast( element );
      }
      this.SetRecycled( element, false );

      m_keyItemDic.Add( newKey, element );

      this.CheckListsIntegrity( element );
    }

    public void Add( T element )
    {
      this.SetRecycled( element, false );
      m_recycledItems.Remove( element );

      this.AddToSourceList( element );

      if( !m_renderedItems.Contains( element ) )
      {
        m_renderedItems.AddLast( element );
      }

      this.CheckListsIntegrity();
    }

    public void Remove( T element )
    {
      Debug.Assert( m_keyItemDic.ContainsKey( this.GetKey(element) ) );
      Debug.Assert( m_renderedItems.Contains( element ) || m_recycledItems.Contains(element) );

      m_keyItemDic.Remove( this.GetKey( element ) );
      if( !this.GetIsRecycled( element ) )
      {
        m_renderedItems.Remove( element );
      }
      else
      {
        m_recycledItems.Remove( element );
      }

      m_removeAction( element );

      this.CheckListsIntegrity();
    }

    public void Recycle( T element )
    {
      Debug.Assert( m_renderedItems.Contains( element ) );
      Debug.Assert( !this.GetIsRecycled( element ) );

      m_renderedItems.Remove( element );
      m_recycledItems.AddLast( element );
      this.SetRecycled( element, true );

      this.CheckListsIntegrity();
    }

    private void AddToSourceList( T element )
    {
      object key = this.GetKey( element );
      if( !m_keyItemDic.ContainsKey( key ) )
      {
        m_keyItemDic.Add( key, element );
        m_addAction( element );
      }
    }

    public bool TryGetRecycledElement( object key, object exceptionKey, out T element )
    {
      element = null;
      if( m_recycledItems.Count > 0 )
      {
        LinkedListNode<T> node = m_recycledItems.Last;
        while( element == null && node != null)
        {
          element = node.Value;
          // If selected Item is the exceptionPath, try to get the next one
          object elementKey = this.GetKey( element );
          if( ( ( exceptionKey != null ) && ( object.Equals( elementKey, exceptionKey ) ) )
            || !this.KeyCompatible( elementKey, key ) )
          {
            element = null;
            node = node.Previous;
          }
        }

        if( element != null )
        {
          m_recycledItems.Remove( element );
          m_keyItemDic.Remove( this.GetKey(element) );
          m_keyItemDic.Add( key, element );
          m_renderedItems.AddLast( element );
          this.SetRecycled( element, false );
        }
      }

      this.CheckListsIntegrity( element );
      return ( element != null );
    }

    private bool KeyCompatible( object key1, object key2 )
    {
      if( !object.Equals( key1.GetType(), key2.GetType() ) )
        return false;

      DataPath keyPath = key1 as DataPath;
      if( keyPath != null )
      {
        return keyPath.Depth == ( ( DataPath )key2 ).Depth;
      }

      return true;
    }

    private void CheckListsIntegrity()
    {
      this.CheckListsIntegrity( null );
    }
    private void CheckListsIntegrity(T elementKeyException)
    {


    }

    //The notification callbacks for "real" add and remove
    private Action<T> m_addAction;
    private Action<T> m_removeAction;

    //For performance. Avoid searching the list for used children (ie. visible)
    private LinkedList<T> m_renderedItems = new LinkedList<T>();

    //For performance. Avoid searching the list for a non used children (ie. collapsed)
    private LinkedList<T> m_recycledItems = new LinkedList<T>();

    //For performance. m_dataPathDic contain the list of items displayed
    //in the container.
    private Dictionary<object, T> m_keyItemDic = new Dictionary<object, T>();
  }
}
